﻿namespace NumericalData.SolvationMethods
open System
open NumericalData
open System.Diagnostics
open NumericalData.Systems
open NumericalData.Exceptions
open NumericalData.Attributes
open NumericalData.SolvationMethods
open NumericalData.SolvationMethods.Async

[<SolvationMethodAnalysisWorkName("Implicit Yakobi Method")>]
type YakobiImplicitMethod = 
    class
    inherit TraceableIterationMethod
    new() = {inherit TraceableIterationMethod()}
    /// <summary>
    /// Solve SLAE using Yakobi method to get B Matrix
    /// and simple iteration method to find answer vector
    /// <param name="sys">System to be solved</param>
    /// <param name="accur"> accurcy - simply iteration method parameter</param>
    /// <exception cref="SLAEInitialMatrixCantBeSolvedException">Matrix can't be solved with this method</exception>
    /// </summary>
    override x.Solve system accur startIsZero maxIterations = 

        let (bMatr, bVec) = YakobiImplicitMethod.CalculateBMatrixAndBVectorWithYakobiMethod system
        

        let (vec,iters) = x.Iterate accur startIsZero maxIterations bMatr bVec


        x.FormResult system vec iters accur 


    /// <summary>
    /// Calculates B Matrix and B Vector using Yakobi method
    /// <param name="sys">System to be solved</param>
    /// <returns> sets fields: x.m_BMatrix x.m_BVector x.m_consumedTime </returns>
    /// <exception cref="SLAEInitialMatrixCantBeSolvedException">Produced B Matrix Rate less than 1</exception>
    /// </summary>
    static member private CalculateBMatrixAndBVectorWithYakobiMethod (sys:SLAE) = 
        let distributionList = YakobiImplicitMethod.GetRowColumnDistributionList sys.InitialMatrix YakobiImplicitMethod.GetMaxAvailableElementColumnIndex

        let (matr:SquareMatrix,vec) = YakobiImplicitMethod.GetUnsortedBMatrixAndBVectorUsingDistributionList (sys.InitialMatrix,sys.InitialVector) distributionList
            
        YakobiImplicitMethod.SortRowsUsingDistributionList (matr,vec) distributionList

        if Rates.MRate(matr) > 1. then
            raise <| SLAEInitialMatrixCantBeSolvedException

        matr@*(-1.)
        (matr,vec)
            

    static member private GetRowColumnDistributionList (matrix:SquareMatrix)
            (searchMethod) = 
            let availableCols = new System.Collections.Generic.List<int>(seq{0..(matrix.ColumnsNumber-1)})
            let passedCols = new System.Collections.Generic.List<int*int>()
            for i in 0..(matrix.RowsNumber - 1) do
                let curVec = matrix.GetRowVector(i)
                let foundIndex = searchMethod curVec availableCols passedCols 
                if curVec.[foundIndex] = 0. then
                    raise <| SLAEInitialMatrixHasZeroAsMaxAbsoluteElementException(i,foundIndex)
                passedCols.Add((i,foundIndex))
                availableCols.Remove(foundIndex) |> ignore  
            passedCols  

    static member private GetUnsortedBMatrixAndBVectorUsingDistributionList ((matrix:SquareMatrix), (b:Vector))
                    (distributionList:System.Collections.Generic.List<int*int>) = 
        let newbVec  = b.Clone()
        let newDoubleArray =
            [| 
                for (rowi,coli) in distributionList do
                    let row = matrix.GetRowVector(rowi)
                    newbVec.[rowi] <- newbVec.[rowi]/matrix.[rowi,coli]
                    row@/matrix.[rowi,coli]
                    row.[coli] <- 0.
                    yield row.InnerArray
            |]
        (
            new SquareMatrix(newDoubleArray),
            newbVec
        )

    static member private GetMaxAvailableElementColumnIndex (curVec:Vector) 
                (availableCols:System.Collections.Generic.List<int>) 
                (passedCols:System.Collections.Generic.List<int*int>) = 
        let mutable maxI = availableCols.[0]
        let mutable found = false
        let mutable watched = 0
        for index in availableCols do
            if (passedCols.FindIndex (fun (row,col) -> col = index)) = -1  then
                if Math.Abs(curVec.[maxI]) < Math.Abs(curVec.[index])  then
                    maxI <- index
                    found <- true
                else
                    if watched = (availableCols.Count-1) && found = false then
                        maxI <-index
        //printfn "found max index %d" maxI
        maxI

    static member private SortRowsUsingDistributionList ((matrix:SquareMatrix), (b:Vector))
                (distributionList:System.Collections.Generic.List<int*int>) = 
            let sortDistributionList (distributionList:System.Collections.Generic.List<int*int>) = 
                let out = new System.Collections.Generic.List<int*int>(distributionList)
                out.Sort( new Comparison<int*int>( fun (r1,c1) (r2,c2)  -> c1.CompareTo(c2) ))
                out
            let mutable sortList = sortDistributionList distributionList
            //SLAE.PrintDistributionList sortList
            for i in 0..(sortList.Count - 1 ) do 
                let (from_,to_) = sortList.[0] 
                if from_ = to_ then 
                    ()
                else
                    if from_ <= i then 
                        ()
                    else
                        b.ReplaceElements from_ to_
                        matrix.ReplaceRows from_ to_
                        //printfn "replacing %d with %d" from_ to_
                        let idx = sortList.FindIndex (fun (row,col) -> row = to_)
                        //printfn "changed: %d on %d" (fst sortList.[idx]) from_
                        sortList.[idx] <- (from_ ,snd sortList.[idx])
                sortList.RemoveAt(0)
            matrix.Print()

    interface IAsyncSolvable<DiagnosticsAnswer> with 
        member x.AsyncSolve system accur startIsZero maxIterations asyncSuccess asyncFail asyncCancel = 
            let loppedSolveMethod = x.TraceSolve system accur startIsZero
            AsyncSolveFactory.Generate<int,DiagnosticsAnswer> loppedSolveMethod maxIterations asyncSuccess asyncFail asyncCancel
       
    member x.AsyncSolve  = (x :> IAsyncSolvable<DiagnosticsAnswer>).AsyncSolve
    end